/*
 * Copyright (c) 2017, www.zm0618.com All Rights Reserved..
 */

package com.light.hessian.remoting.client.core;

import com.caucho.hessian.HessianException;
import com.caucho.hessian.client.HessianConnectionException;
import com.caucho.hessian.client.HessianRuntimeException;
import com.light.hessian.remoting.client.balance.Balance;
import com.light.hessian.provider.config.ProviderStatus;
import com.light.hessian.provider.slot.ProviderRunStatus;
import com.light.hessian.provider.slot.ProviderStatusSlotPiple;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.remoting.RemoteAccessException;
import org.springframework.remoting.RemoteConnectFailureException;
import org.springframework.remoting.RemoteProxyFailureException;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.UndeclaredThrowableException;
import java.net.ConnectException;
import java.net.MalformedURLException;
import java.util.HashMap;
import java.util.Map;

/**
 * Created by zhaojun on 2017/02/10.
 */
public class HessianClientInterceptor extends ServiceBasedRemoteAccessor implements MethodInterceptor,ApplicationContextAware {

    protected Map<String,ProxyObjHolder> proxyObjHolderCache =new HashMap<String, ProxyObjHolder>();

    private String reference;
    private Balance balance;

    private volatile ProviderStatusSlotPiple statusSlotPiple;

    private ApplicationContext applicationContext;

    protected ProviderRunStatus runStatus=new ProviderRunStatus();


    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {

        ClassLoader originalClassLoader = overrideThreadContextClassLoader();
        initStatusSlotPiple();//
        ProxyObjHolder proxyObjHolder= loadLazyProxyObjHolder(statusSlotPiple,this.balance);
        try {
            Object rtn = invokeProcess(invocation, proxyObjHolder);
            return rtn;
        }
        catch (InvocationTargetException ex) {

            Throwable targetEx = ex.getTargetException();
            logger.debug(ex.getMessage());

            if (targetEx instanceof InvocationTargetException) {
                targetEx = ((InvocationTargetException) targetEx).getTargetException();
            }

            if (targetEx instanceof HessianConnectionException) {
                throw convertHessianAccessException(targetEx);
            }
            else if (targetEx instanceof HessianException) {
                Throwable cause = targetEx.getCause();
                throw convertHessianAccessException(cause);
            }
            else if(targetEx instanceof HessianRuntimeException){
                return handleHessianRuntimeEx(invocation, proxyObjHolder, targetEx);
            }
            else if (targetEx instanceof UndeclaredThrowableException) {
                UndeclaredThrowableException utex = (UndeclaredThrowableException) targetEx;
                throw convertHessianAccessException(utex.getUndeclaredThrowable());
            }
            else {
                throw targetEx;
            }
        }
        catch(Throwable ex){
            throw new RemoteProxyFailureException(
                    "Failed to invoke Hessian proxy for remote service [" + getServiceName() + "]", ex);
        }
        finally {
            resetThreadContextClassLoader(originalClassLoader);
        }

    }
    private Object handleHessianRuntimeEx(MethodInvocation invocation, ProxyObjHolder proxyObjHolder, Throwable targetEx) throws Throwable {
        HessianRuntimeException hessianRuntimeException= (HessianRuntimeException) targetEx;
        Throwable rootCause = hessianRuntimeException.getRootCause();
        if (rootCause instanceof ConnectException){//远程服务器下线 重试
           statusSlotPiple.reportSlotStatus(proxyObjHolder.getProviderInfo(), ProviderStatus.DOWN);
           return  this.invoke(invocation);
        }
        throw convertHessianAccessException(targetEx);
    }

    private Object invokeProcess(MethodInvocation invocation, ProxyObjHolder proxyObjHolder) throws IllegalAccessException, InvocationTargetException {
        Object hessianProxy=proxyObjHolder.getProxyObj();
        ProviderRunStatus currentRunStatus = runStatus.clone();
        currentRunStatus.setBalance(balance);
        currentRunStatus.setProviderInfo(proxyObjHolder.getProviderInfo());
        currentRunStatus.setStartTime(System.currentTimeMillis());
        Object rtn = invocation.getMethod().invoke(hessianProxy, invocation.getArguments());
        currentRunStatus.setEndTime(System.currentTimeMillis());
        this.statusSlotPiple.reportSlotRuntime(currentRunStatus);//上报当前运行状态
        return rtn;
    }

    protected ProxyObjHolder loadLazyProxyObjHolder(ProviderStatusSlotPiple slotPiple, Balance balance) throws MalformedURLException {return null;};



    protected RemoteAccessException convertHessianAccessException(Throwable ex) {
        if (ex instanceof HessianConnectionException || ex instanceof ConnectException) {
            return new RemoteConnectFailureException(
                    "Cannot connect to Hessian remote service at [" + getServiceName() + "]", ex);
        }
        else {
            return new RemoteAccessException(
                    "Cannot access Hessian remote service at [" + getServiceName() + "]", ex);
        }
    }


    @Override
    public void afterPropertiesSet() {
        super.afterPropertiesSet();
    }


    public ProviderStatusSlotPiple initStatusSlotPiple(){
        if (statusSlotPiple==null){
            synchronized (this){
                if (statusSlotPiple==null){
                    statusSlotPiple= (ProviderStatusSlotPiple) applicationContext.getBean(this.getReference());
                }
            }
        }
        return statusSlotPiple;
    }

    public String getReference() {
        return reference;
    }

    public void setReference(String reference) {
        this.reference = reference;
    }

    public Balance getBalance() {
        return balance;
    }

    public void setBalance(Balance balance) {
        this.balance = balance;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
          this.applicationContext=applicationContext;
    }
}
